/*!	 cairo_renddesc.cpp
 **	 Implementation of Cairo helper functions with RendDesc
 **
 **	Copyright (c) 2013 Carlos López
 **
 **	This package is free software; you can redistribute it and/or
 **	modify it under the terms of the GNU General Public License as
 **	published by the Free Software Foundation; either version 2 of
 **	the License, or (at your option) any later version.
 **
 **	This package is distributed in the hope that it will be useful,
 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 **	General Public License for more details.
 **
 */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "cairo_renddesc.h"
#include "general.h"
#include <synfig/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;

bool
cairo_renddesc_untransform(cairo_t* cr, RendDesc &renddesc)
{
    const Real	pw = renddesc.get_pw(),	ph = renddesc.get_ph();

    const Point	tl(renddesc.get_tl());
    const Point br(renddesc.get_br());

    double tl_x, tl_y, tr_x, tr_y, bl_x, bl_y, br_x, br_y;
    double mtlx, mtly, mbrx, mbry;
    double pminx, pminy, pmaxx, pmaxy;

    const int flags = renddesc.get_flags();

    tl_x = tl[0];
    tl_y = tl[1];
    br_x = br[0];
    br_y = br[1];
    tr_x = br_x;
    tr_y = tl_y;
    bl_x = tl_x;
    bl_y = br_y;

    RendDesc	workdesc(renddesc);
    // In this block we are going to calculate the inversed transform of the
    // workdesc but not applying the transformation to convert the surface to
    // device space (See the cairo translate and scale on Target_Cairo::render and
    // Target_Cairo_Tile::render)

    // Extract the matrix from the current context
    cairo_matrix_t cr_matrix, cr_result;
    cairo_get_matrix(cr, &cr_matrix);

    // Now create three matrixes with the following values:
    // resulting matrix result=i_translate*i_scale
    // inverse translation i_translate = inverse translation from -renddesc_tl
    // inverse scale i_scale = inverse scale of 1/pw and 1/ph

    cairo_matrix_t i_scale, i_translate, result;
    cairo_matrix_init_translate(&i_translate, tl[0], tl[1]);
    cairo_matrix_init_scale(&i_scale, pw, ph);

    // Now multiply the two matrixes, the order is important!
    // first apply scale and then rotate, the inverse than done in Target_Cairo::render

    cairo_matrix_multiply(&result, &i_scale, &i_translate);

    // Now let's multiply the cr matrix retrieved and the result matrix

    cairo_matrix_multiply(&cr_result, &cr_matrix, &result);

    // Explanation:
    // Current cairo context matrix is this of this form:
    // [T][S][DRAW] where the [T][S] parts corresponds to convert the cairo operations
    // in DRAW part into the device space (usually the image surface of size w, h)
    // DRAW matrix is the result of the layer transformations stack (rotate, zoom, etc.)
    // But we want to transformm the render desc with the inverse of the DRAW part only,
    // not the inverse of the T and S part because we are transforming user coordinates
    // the renddesc and not pixels.
    // So we retrieve the cairo context matrix: [CR]=[T][S][DRAW] and remove the [T] and
    // [S] matrixes by applying its inverses: (the notation ' denotes inverse)
    // [S'][T'][CR]=[S'][T'][T][S][DRAW]=[S'][I][S][DRAW]=[I][DRAW]=[DRAW] as we wanted.
    // [M'][M]=[I] where I is the identity matrix.


    // Now let's invert the result matrix, that is calculate [DRAW']
    cairo_status_t status;
    status = cairo_matrix_invert(&cr_result);

    if (status) { // doh! the matrix can't be inverted! I can't render the surface!
        synfig::error("Can't invert current Cairo matrix!");
        return false;
    }

    // Now let's tranform the renddesc corners with the calculated matrix
    cairo_matrix_transform_point(&cr_result, &tl_x, &tl_y);
    cairo_matrix_transform_point(&cr_result, &tr_x, &tr_y);
    cairo_matrix_transform_point(&cr_result, &bl_x, &bl_y);
    cairo_matrix_transform_point(&cr_result, &br_x, &br_y);

    // Now let's figure out the rounding box of the transformed renddesc
    pminx = min(min(min(tl_x, tr_x), bl_x), br_x);
    pminy = min(min(min(tl_y, tr_y), bl_y), br_y);
    pmaxx = max(max(max(tl_x, tr_x), bl_x), br_x);
    pmaxy = max(max(max(tl_y, tr_y), bl_y), br_y);
    // let's assign the right values to the meaningfull variables :)
    mtlx = pminx;
    mtly = pmaxy;
    mbrx = pmaxx;
    mbry = pminy;

    // Now apply the new tl and br values to the workdesc
    // We don't want to render more pixels than the needed by the original
    // renddesc, so we are going to keep the number of pixels in the diagonal
    // to coincide with the original diagonal
    Vector m_diagonal(br_x - tl_x, br_y - tl_y);
    Vector m_diagonal_bbox(mbrx - mtlx, mbry - mtly);
    double diagonal = m_diagonal.mag();
    double diagonal_bbox = m_diagonal_bbox.mag();
    int w_new = diagonal_bbox / diagonal * renddesc.get_w();
    int h_new = diagonal_bbox / diagonal * renddesc.get_h();
    workdesc.clear_flags();
    // finally apply the new desc values!
    workdesc.set_tl_br(Point(mtlx, mtly), Point(mbrx, mbry));
    workdesc.set_w(w_new);
    workdesc.set_h(h_new);

    renddesc = workdesc;

    renddesc.set_flags(flags);

    return true;
}